﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace ArgusLib.InteropServices
{
	public static class ArgusMarshalerCookies
	{
		public const string SkipNativeCleanUp = "SkipNativeCleanUp";
		public const string SkipManagedCleanUp = "SkipManagedCleanUp";
		private static readonly string[] Delimiter = new string[] { ";" };

		public static string Combine(params string[] cookies)
		{
			if (cookies.Length < 1)
				return string.Empty;
			StringBuilder sb = new StringBuilder();
			sb.Append(cookies[0]);
			for (int i = 1; i < cookies.Length; i++)
			{
				if (string.IsNullOrEmpty(cookies[i]))
					continue;
				sb.Append(ArgusMarshalerCookies.Delimiter[0]);
				sb.Append(cookies[i]);
			}
			return sb.ToString();
		}

		public static string[] Split(string cookies)
		{
			return cookies.Split(ArgusMarshalerCookies.Delimiter, StringSplitOptions.RemoveEmptyEntries);
		}
	}

	public class ArgusMarshaler<T> : ICustomMarshaler
		where T:class, INativeWrapper,new()
	{
		Dictionary<IntPtr, T> dictWrappers;
		int nativeSize;

		public bool SkipNativeCleanUp { get; private set; }
		public bool SkipManagedCleanUp { get; private set; }

		public ArgusMarshaler()
		{
			this.dictWrappers = new Dictionary<IntPtr, T>();
			this.nativeSize = -1;
			this.SkipManagedCleanUp = false;
			this.SkipNativeCleanUp = false;
		}

		public static ICustomMarshaler GetInstance(string cookie)
		{
			ArgusMarshaler<T> cm = new ArgusMarshaler<T>();
			string[] cookies = ArgusMarshalerCookies.Split(cookie);
			if (cookies.Contains(ArgusMarshalerCookies.SkipNativeCleanUp) == true)
				cm.SkipNativeCleanUp = true;
			if (cookies.Contains(ArgusMarshalerCookies.SkipManagedCleanUp) == true)
				cm.SkipManagedCleanUp = true;
			return cm;
		}

		public T MarshalNativeToManaged(IntPtr native)
		{
#if Log
			string stackTrace = System.Environment.StackTrace;
			int i = stackTrace.IndexOf("ArgusLib.FFmpeg.");
			if (i > -1)
			{
				int e = stackTrace.IndexOf('\n', i);
				Log.WriteLine("MarshalNativeToManaged: native = " + native.ToString());
				Log.WriteLine('\t' + stackTrace.Substring(i, e - i));
			}
#endif
			if (native == IntPtr.Zero)
				return null;

			T wrapper;
			if (this.dictWrappers.TryGetValue(native, out wrapper) == true)
			{
				if (wrapper.IsDisposed == false)
					Marshal.PtrToStructure(wrapper.Pointer, wrapper.Structure);
				else
					wrapper.Initialize(Marshal.PtrToStructure(native, wrapper.StructureType), native);
			}
			else
			{
				wrapper = new T();
				wrapper.Initialize(Marshal.PtrToStructure(native, wrapper.StructureType), native);
				this.dictWrappers.Add(native, wrapper);
				if (this.nativeSize < 0)
					this.nativeSize = Marshal.SizeOf(wrapper.StructureType);
			}

			return wrapper;
		}

		object ICustomMarshaler.MarshalNativeToManaged(IntPtr native)
		{
			return this.MarshalNativeToManaged(native);
		}

		public IntPtr MarshalManagedToNative(T wrapper)
		{
#if Log
			string stackTrace = System.Environment.StackTrace;
			int i = stackTrace.IndexOf("ArgusLib.FFmpeg.");
			if (i > -1)
			{
				int e = stackTrace.IndexOf('\n', i);
				Log.WriteLine("MarshalManagedToNative: wrapper = " + wrapper.ToString()+", "+wrapper.Pointer.ToString());
				Log.WriteLine('\t' + stackTrace.Substring(i, e - i));
			}
#endif
			if (wrapper == null)
				return IntPtr.Zero;

			if (wrapper.IsDisposed == true)
				return IntPtr.Zero;

			if (this.nativeSize < 0)
				this.nativeSize = Marshal.SizeOf(wrapper.StructureType);

			if (this.dictWrappers.ContainsKey(wrapper.Pointer) == false)
			{
				this.dictWrappers.Add(wrapper.Pointer, wrapper);
			}
			else if (wrapper != this.dictWrappers[wrapper.Pointer])
			{
				this.dictWrappers[wrapper.Pointer] = wrapper;
			}

			Marshal.StructureToPtr(wrapper.Structure, wrapper.Pointer, false);
			return wrapper.Pointer;
		}

		IntPtr ICustomMarshaler.MarshalManagedToNative(object managed)
		{
			if (managed == null)
				return IntPtr.Zero;

			if (!(managed is T))
				throw new ArgumentException("managed");

			T wrapper = (T)managed;
			return this.MarshalManagedToNative(wrapper);
		}

		public void CleanUpNativeData(IntPtr native)
		{
			if (this.SkipNativeCleanUp == true)
				return;
#if Log
			string stackTrace = System.Environment.StackTrace;
			int i = stackTrace.IndexOf("ArgusLib.FFmpeg.");
			if (i > -1)
			{
				int e = stackTrace.IndexOf('\n', i);
				Log.WriteLine("CleanUpNativeData: native = " + native.ToString());
				Log.WriteLine('\t' + stackTrace.Substring(i, e - i));
			}
#endif
			if (native == IntPtr.Zero)
				return;

			T wrapper;
			if (this.dictWrappers.TryGetValue(native, out wrapper) == true)
			{
				Marshal.PtrToStructure(wrapper.Pointer, wrapper.Structure);
			}
			else
				throw new Exception("Test");
		}

		public void CleanUpManagedData(object managed)
		{
			if (this.SkipManagedCleanUp == true)
				return;
#if Log
			string stackTrace = System.Environment.StackTrace;
			int i = stackTrace.IndexOf("ArgusLib.FFmpeg.");
			if (i > -1)
			{
				int e = stackTrace.IndexOf('\n', i);
				Log.WriteLine("CleanUpManagedData: managed = " + managed.ToString());
				Log.WriteLine('\t' + stackTrace.Substring(i, e - i));
			}
#endif
			if (managed == null)
				return;

			if (!(managed is T))
				throw new ArgumentException("managed");

			T wrapper = (T)managed;
			Marshal.StructureToPtr(wrapper.Structure, wrapper.Pointer, false);
		}

		public int GetNativeDataSize()
		{
			return this.nativeSize;
		}
	}
}
